//-----------------------------------------------------------------------
// <copyright file="SqlLoginDeleteTests.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Barry Dorrans</author>
// <email>barryd@charteris.com</email>
// <date>2004-06-07</date>
// <summary>Unit test cases for the SqlServer deleteLogin action.</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Test
{
    using System;
    using System.IO;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Microsoft.Sdc.Tasks.Configuration;
    using Microsoft.Sdc.Tasks;
    using Microsoft.Build.Framework;


    // We need these to create suitable users to add into SQL
    using Microsoft.Sdc.Tasks.Configuration.ActiveDirectory;
    using Microsoft.Sdc.Tasks.ActiveDirectory;

    // And we need these to impersonate users without adequate permissions in SQL to add people
    using System.Runtime.InteropServices;
    using System.Security.Principal;
    using System.Security.Permissions;

    // And we need these to check our results
    using System.Data;
    using System.Data.SqlClient;
    using System.Data.SqlTypes;

    /// <summary>
    /// Unit tests for Tasks.Sql.Role.AddUser scenarios
    /// </summary>
    [TestClass]
    public class SqlAccessRevokeTests
    {
       
        private string _testUserName = String.Empty;
        private string _testPassword = "1234$abcd";

        [TestInitialize]
        public void InitBuildEngineInterface()
        {
            this.CreateUserName();
        }

        /// <summary>
        /// Create a new, random(ish) local NT account for testing
        /// </summary>
        /// <remarks>
        /// Note that this relies on the ActiveDirectory.User.Create task functioning
        /// as expected.
        /// </remarks>
        private void CreateUserName()
        {
            Guid guid = Guid.NewGuid();
            string username = guid.ToString();
            username = username.Replace("-", "").Substring(0, 11);

            Console.WriteLine("Test Setup, creating test username " + username);

            if (User.Exists(username))
                Assert.Fail("Temporary user already exists.");
            // Now create the user, using the CreateActiveDirectoryUser task
            Tasks.ActiveDirectory.User.Create adTask = new Tasks.ActiveDirectory.User.Create();
            adTask.UserName = username;
            adTask.Password = _testPassword;
            bool taskReturnValue = adTask.Execute();
            Assert.IsTrue(taskReturnValue, "Cannot create temporary user.");
            // Check the user now exists
            bool userExists = User.Exists(username);
            Assert.IsTrue(userExists, "Temporary user existance check.");

            _testUserName = username;
        }

        /// <summary>
        /// Clean up the previously created testing username
        /// </summary>
        /// <remarks>
        /// As this has the [TearDown] attribute, nunit will run it at the end of each test
        /// </remarks>
        [TestCleanup]
        public void CleanUserName()
        {
            Console.WriteLine("Test Teardown, deleting test username " + this._testUserName);
            User user = User.Load(this._testUserName);
            user.Delete();
        }

        public SqlAccessRevokeTests()
        {
        }

        /// <summary>
        /// Check the default properties after object create, and then the property set/get methods
        /// </summary>
        [TestMethod]
        public void TestProperties()
        {
            Tasks.Sql.Access.Revoke task = new Tasks.Sql.Access.Revoke();
            
            task.ServerName = "(servername)";
            if (task.ServerName != "(servername)")
                Assert.Fail("server name property set/get failed.");

            task.UserName = "(username)";
            if (task.UserName != "(username)")
                Assert.Fail("user name property set/get failed.");

        }

        /// <summary>
        /// Test user deletion against local server, with no server property set
        /// </summary>
        [Ignore]//("Not running on Yukon yet")]
        [TestMethod]
        public void TestUserDeletionAgainstLocalServer()
        {
            Tasks.Sql.Access.Revoke task = new Tasks.Sql.Access.Revoke();
            
            // Add the temporary user into the allowed logins table
            SqlConnection dbConnection = new SqlConnection("Data Source=(local);" +
                            "Initial Catalog=master;" +
                            "Integrated Security=SSPI");
            dbConnection.Open();
            SqlCommand testCommand = new SqlCommand(
                "sp_grantlogin '" +
                System.Environment.MachineName + @"\" + this._testUserName+"'",
                dbConnection);
            testCommand.ExecuteNonQuery();
            testCommand.CommandText =
                "sp_helplogins '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            SqlDataReader userReader = testCommand.ExecuteReader();
            if (!userReader.HasRows)
                Assert.Fail("Grant login to temporary user failed.");
            userReader.Close();

            task.UserName = System.Environment.MachineName + @"\"+ this._testUserName;
            task.Execute();

            testCommand.CommandText =
                "sp_helplogins '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            userReader = testCommand.ExecuteReader();
            bool userDeleted = !userReader.HasRows;

            userReader.Close();
            userReader.Dispose();
            testCommand.Dispose();
            dbConnection.Close();
            dbConnection.Dispose();

            if (!userDeleted)
                Assert.Fail("Revoke login to temporary user failed.");

        }

        /// <summary>
        /// Test user deletion against a non-existant server
        /// </summary>
        [Ignore]//("Not running on Yukon yet")]
        [TestMethod]
        public void TestUserDeletionAgainstBadServer()
        {
            Tasks.Sql.Access.Revoke task = new Tasks.Sql.Access.Revoke();
            
            // Make up instance name
            // Generate a new, random-ish instance name
            Guid guid = Guid.NewGuid();
            string instanceName = guid.ToString();
            instanceName = System.Environment.MachineName + @"\" + instanceName.Replace("-", "").Substring(0, 11);
            // Now check the instance doesn't actually exist
            try
            {
                SqlConnection dbConnection = new SqlConnection("Data Source=" + instanceName + ";" +
                                "Initial Catalog=master;" +
                                "Integrated Security=SSPI;" +
                                "Connection Timeout=1");
                dbConnection.Open();
                dbConnection.Close();
                Assert.Fail("Random SQL instance name actually exists on this machine.");
            }
            catch (SqlException)
            {
                // Swallow 'em.
            }

            try 
            {
                task.UserName = System.Environment.MachineName + @"\" + this._testUserName;
                task.ServerName = instanceName;
                task.ConnectionTimeout = 1;
                task.Execute();
                Assert.Fail("Deleting a user from a non-existant instance did not fail.");
            }        
            catch (ApplicationException)
            {
                string lastMessage = ((PseudoBuildEngine) task.BuildEngine).PreviousMessage;
                // Make sure the error message was the correct user does not exist message
                Assert.IsTrue(lastMessage.IndexOf("Shared Memory Provider: The system cannot find the file specified.") >= 0);
            }
        }

        /// <summary>
        /// Test user deletion against a sepecific named instance
        /// </summary>
        [Ignore]//("Not running on Yukon yet")]
        [TestMethod]
        public void TestUserDeletionAgainstNamedServer()
        {
            Tasks.Sql.Access.Revoke task = new Tasks.Sql.Access.Revoke();
           
            // Add the temporary user into the allowed logins table
            SqlConnection dbConnection = new SqlConnection("Data Source=" + System.Environment.MachineName + ";" +
                            "Initial Catalog=master;" +
                            "Integrated Security=SSPI");
            dbConnection.Open();
            SqlCommand testCommand = new SqlCommand(
                "sp_grantlogin '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'",
                dbConnection);
            testCommand.ExecuteNonQuery();
            testCommand.CommandText =
                "sp_helplogins '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            SqlDataReader userReader = testCommand.ExecuteReader();
            if (!userReader.HasRows)
                Assert.Fail("Grant login to temporary user failed.");
            userReader.Close();

            task.UserName = System.Environment.MachineName + @"\" + this._testUserName;
            task.ServerName = System.Environment.MachineName;
            task.Execute();

            testCommand.CommandText =
                "sp_helplogins '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            userReader = testCommand.ExecuteReader();
            bool userDeleted = !userReader.HasRows;

            userReader.Close();
            userReader.Dispose();
            testCommand.Dispose();
            dbConnection.Close();
            dbConnection.Dispose();

            if (!userDeleted)
                Assert.Fail("Revoke login to temporary user failed.");
        }


        /// <summary>
        /// Test user deletion with a user that does not exist
        /// </summary>
        /// <remarks>
        /// This is simply ran against (local)
        /// </remarks>        
        [Ignore]//("Not running on Yukon yet")]
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void TestUserDeletionWithNonExistentUser()
        {
            Tasks.Sql.Access.Revoke task = new Tasks.Sql.Access.Revoke();
            
            task.UserName = System.Environment.MachineName + @"\" + _testUserName;
            task.Execute();
        }

        /// <summary>
        /// Test user deletion specifying SA as the username
        /// </summary>
        /// <remarks>
        /// This is simply ran against (local)
        /// </remarks>        
        [Ignore]//("Not running on Yukon yet")]
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void TestUserDeletionWithSA()
        {
            Tasks.Sql.Access.Revoke task = new Tasks.Sql.Access.Revoke();
           
            task.UserName = "sa";
            task.Execute();
        }

        /// <summary>
        /// Test user deletion in a security context which does not have rights to delete
        /// </summary>
        [Ignore]//("Not running on Yukon yet")]
        [TestMethod]
        public void TestUserDeletionWithoutPermissions()
        {
            // Add the temporary user into the allowed logins table
            SqlConnection dbConnection = new SqlConnection("Data Source=(local);" +
                            "Initial Catalog=master;" +
                            "Integrated Security=SSPI");
            dbConnection.Open();
            SqlCommand testCommand = new SqlCommand(
                "sp_grantlogin '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'",
                dbConnection);
            testCommand.ExecuteNonQuery();
            testCommand.CommandText =
                "sp_helplogins '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            SqlDataReader userReader = testCommand.ExecuteReader();
            if (!userReader.HasRows)
                Assert.Fail("Grant login to temporary user failed.");
            userReader.Close();

            // Now, swap to that temporary user.

            // Now switch context to this user
            const int LOGON32_LOGON_INTERACTIVE = 2;
            const int LOGON32_PROVIDER_DEFAULT = 0;
            IntPtr userHandle = new IntPtr(0);
            bool returnValue = NativeMethods.LogonUser(this._testUserName,
                System.Environment.MachineName,
                this._testPassword,
                LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref userHandle);

            if (!returnValue)
                throw new Exception("Invalid Username");
            WindowsIdentity newId = new WindowsIdentity(userHandle);
            WindowsImpersonationContext impersonatedUser = newId.Impersonate();
            Console.WriteLine("Now running in context of : " +
                            WindowsIdentity.GetCurrent().Name);
            // Now try it, using the previously constructed task, for neatness
            Tasks.Sql.Access.Revoke contextTask = new Tasks.Sql.Access.Revoke();
                
            try
            {
                
                contextTask.UserName = this._testUserName;

                contextTask.Execute();
            }
            catch (ApplicationException)
            {
                string lastMessage = ((PseudoBuildEngine)contextTask.BuildEngine).PreviousMessage;
                // Make sure the error message was the correct no permissions
                Assert.IsTrue(lastMessage.IndexOf("User does not have permission to perform this action.") >= 0);
            }
            finally
            {
                try
                {
                    impersonatedUser.Undo();
                    if (userHandle != IntPtr.Zero)
                        NativeMethods.CloseHandle(userHandle);
                }
                catch
                {
                    throw;
                }
            }

            // Now cleanup
            testCommand.CommandText =
                "sp_revokelogin '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            testCommand.ExecuteNonQuery();
            testCommand.CommandText =
                "sp_helplogins '" +
                System.Environment.MachineName + @"\" + this._testUserName + "'";
            userReader = testCommand.ExecuteReader();
            bool bRevoked = !userReader.HasRows;
            userReader.Close();
            userReader.Dispose();
            testCommand.Dispose();
            dbConnection.Close();
            dbConnection.Dispose();
            if (!bRevoked)
                Assert.Fail("sp_revokelogin failed on temp user");

        }
    
        internal class NativeMethods
        {
            [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
            public static extern bool LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
            [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
            public extern static bool CloseHandle(IntPtr handle);
        }
    }
}
